package main

import (
	"encoding/csv"
	"fmt"
	"gitee.com/trackertrader/goctp"
	ctp "gitee.com/trackertrader/goctp/win"
	"gitee.com/trackertrader/rpcx/log"
	"gitee.com/trackertrader/trackersdk/common"
	"gitee.com/trackertrader/trackersdk/utils"
	"github.com/robfig/cron"
	"github.com/urfave/cli/v2"
	"github.com/wxnacy/wgo/arrays"
	"os"
	"strconv"
	"strings"
	"time"
)

// 行情服务配置
type Config struct {
	Host    string   `toml:"host"`
	Symbols []string `toml:"symbols"`
	Ctp     CtpConfig
}

// CTP配置
type CtpConfig struct {
	QuoteHost  string `toml:"quotehost"`
	TradeHost  string `toml:"tradehost"`
	BrokerID   string `toml:"brokerid"`
	InvestorID string `toml:"investorid"`
	Password   string `toml:"password"`
	AppID      string `toml:"appid"`
	AuthCode   string `toml:"authcode"`
}

/**
 * @Description: quote server
 */
type QuoteRecorder struct {
}

// 声明行情服务
var QuoteService *QuoteRecorder

// ctp行情
var CtpQuote *ctp.Quote

// ctp是否登录成功
var CtpLogin = false

// 配置文件
var Conf Config

// 创建行情服务
func newQuoteRecorder() *QuoteRecorder {
	s := new(QuoteRecorder)
	return s
}

/**
 * @Description: 订阅合约代码
 * @receiver s
 */
func SubscribeAll() {
	for _, v := range Conf.Symbols {
		log.Infof("%s", v)
		CtpQuote.ReqSubscript(v)
	}
	log.Info("更新订阅合约列表")
}

/**
 * @Description: 启动行情服务
 * @receiver *QuoteRecorder
 * @param cctx
 * @return error
 */
func (s *QuoteRecorder) Start(cctx *cli.Context) error {
	// 启动CTP
	s.StartCTP()

	// 启动检测ctp
	go func() {
		for {
			time.Sleep(time.Second * 10)
			if !CtpLogin {
				log.Warnf("ctp disconnected, retry connect & login")
				CtpQuote.Release()
				s.StartCTP()
			}
		}
	}()

	// 启动定时更新订阅任务
	go func() {
		task := cron.New()

		// 早上8:40更新一次
		_ = task.AddFunc("00 40 08 * * *", SubscribeAll)

		// 晚上20:40更新一次
		_ = task.AddFunc("00 40 20 * * *", SubscribeAll)

		task.Start()
	}()

	log.Info("quote recorder start success")
	return nil
}

/**
 * @Description: 启动CTP
 * @receiver s
 */
func (s *QuoteRecorder) StartCTP() {
	log.Info("start ctp")
	CtpQuote = ctp.NewQuote()
	CtpQuote.RegOnFrontConnected(s.OnFrontConnected)
	CtpQuote.RegOnFrontDisConnected(s.OnFrontDisConnected)
	CtpQuote.RegOnRspUserLogin(s.OnRspUserLogin)
	CtpQuote.RegOnTick(s.OnTick)
	CtpQuote.ReqConnect(Conf.Ctp.QuoteHost)
}

/**
 * @Description: CTP连接成功
 * @receiver s
 */
func (s *QuoteRecorder) OnFrontConnected() {
	log.Info("ctp connected & do login")
	CtpQuote.ReqLogin(Conf.Ctp.InvestorID, Conf.Ctp.Password, Conf.Ctp.BrokerID)
}

/**
 * @Description: CTP断开连接
 * @receiver s
 */
func (s *QuoteRecorder) OnFrontDisConnected(reason int) {
	log.Infof("ctp disconnected reason: %+v", reason)
	CtpLogin = false
}

/**
 * @Description: CTP用户登录
 * @receiver s
 */
func (s *QuoteRecorder) OnRspUserLogin(login *goctp.RspUserLoginField, info *goctp.RspInfoField) {
	log.Infof("ctp user login: %+v", *login)
	if 0 == info.ErrorID {
		CtpLogin = true
		log.Infof("login success: %+v", *info)

		// 订阅所有合约
		SubscribeAll()
		log.Infof("subscribe all done")
	} else {
		log.Errorf("login fail: %+v", *info)
	}
}

/**
 * @Description: 接收CTP行情
 * @receiver s
 * @param data
 */
func (s *QuoteRecorder) OnTick(data *goctp.TickField) {
	if "" == data.ActionDay {
		return
	}

	// 转换时间戳
	datetimeStr := data.ActionDay + " " + data.UpdateTime
	timeStr, err := time.ParseInLocation("20060102 15:04:05", datetimeStr, time.Local)
	if nil != err {
		log.Error(err)
		return
	}
	timestamp := (timeStr.Unix() * 1000) + int64(data.UpdateMillisec)

	// 分离出合约品种
	symbolT := strings.Trim(data.InstrumentID, "1234567890")

	// 时间数值
	Hour := timeStr.Hour()
	Minutes := timeStr.Minute()
	Seconds := timeStr.Second()
	xtime := Hour*3600 + Minutes*60 + Seconds

	// 过滤掉该时间区间的垃圾数据
	if 15*3600+1 <= xtime && xtime < 15*3600+15*60+1 {
		if 0 >= arrays.ContainsString([]string{"T", "TF", "TS"}, symbolT) {
			return
		}
	}
	if 2*3600+30*60+1 <= xtime && xtime < 8*3600+59*60+59 {
		return
	}
	if 15*3600+15*60+1 <= xtime && xtime < 20*3600+59*60+59 {
		return
	}
	if 0 == data.Volume {
		return
	}

	// 处理夜盘行情，当(20:00 <= 行情内时间 <= 23:00)取本地本地-1小时的日期
	if (20 <= Hour) && (Hour <= 23) {
		localtime := time.Now().Add(-time.Hour).Unix()
		actionday := time.Unix(localtime, 0).Format("20060102")
		datetimeStr := actionday + " " + data.UpdateTime
		timeStr, err := time.ParseInLocation("20060102 15:04:05", datetimeStr, time.Local)
		if nil != err {
			log.Error(err)
			return
		}
		timestamp = (timeStr.Unix() * 1000) + int64(data.UpdateMillisec)
	}

	// 过滤过期数据
	if 30000 < utils.Abs(time.Now().Unix()*1000-timestamp) {
		return
	}

	// 处理价格
	if 15 < len(fmt.Sprint(data.AskPrice5)) {
		if "1.7976931348623" == fmt.Sprint(data.AskPrice5)[0:15] {
			data.AskPrice5 = 0
		}
	}
	if 15 < len(fmt.Sprint(data.AskPrice4)) {
		if "1.7976931348623" == fmt.Sprint(data.AskPrice4)[0:15] {
			data.AskPrice4 = 0
		}
	}
	if 15 < len(fmt.Sprint(data.AskPrice3)) {
		if "1.7976931348623" == fmt.Sprint(data.AskPrice3)[0:15] {
			data.AskPrice3 = 0
		}
	}
	if 15 < len(fmt.Sprint(data.AskPrice2)) {
		if "1.7976931348623" == fmt.Sprint(data.AskPrice2)[0:15] {
			data.AskPrice2 = 0
		}
	}
	if 15 < len(fmt.Sprint(data.AskPrice1)) {
		if "1.7976931348623" == fmt.Sprint(data.AskPrice1)[0:15] {
			data.AskPrice1 = 0
		}
	}
	if 15 < len(fmt.Sprint(data.BidPrice1)) {
		if "1.7976931348623" == fmt.Sprint(data.BidPrice1)[0:15] {
			data.BidPrice1 = 0
		}
	}
	if 15 < len(fmt.Sprint(data.BidPrice2)) {
		if "1.7976931348623" == fmt.Sprint(data.BidPrice2)[0:15] {
			data.BidPrice2 = 0
		}
	}
	if 15 < len(fmt.Sprint(data.BidPrice3)) {
		if "1.7976931348623" == fmt.Sprint(data.BidPrice3)[0:15] {
			data.BidPrice3 = 0
		}
	}
	if 15 < len(fmt.Sprint(data.BidPrice4)) {
		if "1.7976931348623" == fmt.Sprint(data.BidPrice4)[0:15] {
			data.BidPrice4 = 0
		}
	}
	if 15 < len(fmt.Sprint(data.BidPrice5)) {
		if "1.7976931348623" == fmt.Sprint(data.BidPrice5)[0:15] {
			data.BidPrice5 = 0
		}
	}
	if 15 < len(fmt.Sprint(data.LastPrice)) {
		if "1.7976931348623" == fmt.Sprint(data.LastPrice)[0:15] {
			data.LastPrice = 0
		}
	}
	if 15 < len(fmt.Sprint(data.HighestPrice)) {
		if "1.7976931348623" == fmt.Sprint(data.HighestPrice)[0:15] {
			data.HighestPrice = 0
		}
	}
	if 15 < len(fmt.Sprint(data.LowestPrice)) {
		if "1.7976931348623" == fmt.Sprint(data.LowestPrice)[0:15] {
			data.LowestPrice = 0
		}
	}
	if 15 < len(fmt.Sprint(data.OpenPrice)) {
		if "1.7976931348623" == fmt.Sprint(data.OpenPrice)[0:15] {
			data.OpenPrice = 0
		}
	}
	if 15 < len(fmt.Sprint(data.AveragePrice)) {
		if "1.7976931348623" == fmt.Sprint(data.AveragePrice)[0:15] {
			data.AveragePrice = 0
		}
	}
	if 15 < len(fmt.Sprint(data.ClosePrice)) {
		if "1.7976931348623" == fmt.Sprint(data.ClosePrice)[0:15] {
			data.ClosePrice = 0
		}
	}
	if 15 < len(fmt.Sprint(data.SettlementPrice)) {
		if "1.7976931348623" == fmt.Sprint(data.SettlementPrice)[0:15] {
			data.SettlementPrice = 0
		}
	}
	if 15 < len(fmt.Sprint(data.UpperLimitPrice)) {
		if "1.7976931348623" == fmt.Sprint(data.UpperLimitPrice)[0:15] {
			data.UpperLimitPrice = 0
		}
	}
	if 15 < len(fmt.Sprint(data.LowerLimitPrice)) {
		if "1.7976931348623" == fmt.Sprint(data.LowerLimitPrice)[0:15] {
			data.LowerLimitPrice = 0
		}
	}
	if 15 < len(fmt.Sprint(data.CurrDelta)) {
		if "1.7976931348623" == fmt.Sprint(data.CurrDelta)[0:15] {
			data.CurrDelta = 0
		}
	}

	// 转换行情
	var quote common.Quote
	quote.Symbol = data.InstrumentID
	quote.SymbolT = symbolT
	quote.Exchange = data.ExchangeID
	quote.Datetime = timestamp
	quote.DatetimeStr = datetimeStr
	quote.AskPrice5 = data.AskPrice5
	quote.AskVolume5 = int64(data.AskVolume5)
	quote.AskPrice4 = data.AskPrice4
	quote.AskVolume4 = int64(data.AskVolume4)
	quote.AskPrice3 = data.AskPrice3
	quote.AskVolume3 = int64(data.AskVolume3)
	quote.AskPrice2 = data.AskPrice2
	quote.AskVolume2 = int64(data.AskVolume2)
	quote.AskPrice1 = data.AskPrice1
	quote.AskVolume1 = int64(data.AskVolume1)
	quote.BidPrice1 = data.BidPrice1
	quote.BidVolume1 = int64(data.BidVolume1)
	quote.BidPrice2 = data.BidPrice2
	quote.BidVolume2 = int64(data.BidVolume2)
	quote.BidPrice3 = data.BidPrice3
	quote.BidVolume3 = int64(data.BidVolume3)
	quote.BidPrice4 = data.BidPrice4
	quote.BidVolume4 = int64(data.BidVolume4)
	quote.BidPrice5 = data.BidPrice5
	quote.BidVolume5 = int64(data.BidVolume5)
	quote.LastPrice = data.LastPrice
	quote.Highest = data.HighestPrice
	quote.Lowest = data.LowestPrice
	quote.Open = data.OpenPrice
	quote.Average = data.AveragePrice
	quote.Volume = int64(data.Volume)
	quote.Amount = data.Turnover
	quote.Close = data.ClosePrice
	quote.OpenInterest = int64(data.OpenInterest)
	quote.UpperLimit = data.UpperLimitPrice
	quote.LowerLimit = data.LowerLimitPrice
	quote.PreOpenInterest = int64(data.PreOpenInterest)
	quote.PreSettlement = data.PreSettlementPrice
	quote.PreClose = data.PreClosePrice
	quote.LocalTimestamp = time.Now().Unix()

	filename := os.Getenv("QUOTERECORDER_HOME") + "/data/" + quote.Symbol + ".csv"
	var f *os.File
	if !utils.IsFileExists(filename) {
		f, err = os.OpenFile(filename, os.O_RDWR|os.O_CREATE, os.ModePerm)
		if nil != err {
			return
		}
		csvwriter := csv.NewWriter(f)

		// 写表头
		var h []string
		h = append(h, "Symbol")
		h = append(h, "SymbolT")
		h = append(h, "Exchange")
		h = append(h, "Datetime")
		h = append(h, "DatetimeStr")
		h = append(h, "AskPrice5")
		h = append(h, "AskVolume5")
		h = append(h, "AskPrice4")
		h = append(h, "AskVolume4")
		h = append(h, "AskPrice3")
		h = append(h, "AskVolume3")
		h = append(h, "AskPrice2")
		h = append(h, "AskVolume2")
		h = append(h, "AskPrice1")
		h = append(h, "AskVolume1")
		h = append(h, "BidPrice1")
		h = append(h, "BidVolume1")
		h = append(h, "BidPrice2")
		h = append(h, "BidVolume2")
		h = append(h, "BidPrice3")
		h = append(h, "BidVolume3")
		h = append(h, "BidPrice4")
		h = append(h, "BidVolume4")
		h = append(h, "BidPrice5")
		h = append(h, "BidVolume5")
		h = append(h, "LastPrice")
		h = append(h, "Highest")
		h = append(h, "Lowest")
		h = append(h, "Open")
		h = append(h, "Average")
		h = append(h, "Volume")
		h = append(h, "Amount")
		h = append(h, "Close")
		h = append(h, "OpenInterest")
		h = append(h, "UpperLimit")
		h = append(h, "LowerLimit")
		h = append(h, "PreOpenInterest")
		h = append(h, "PreSettlement")
		h = append(h, "PreClose")
		h = append(h, "LocalTimestamp")
		_ = csvwriter.Write(h)
		csvwriter.Flush()
	} else {
		f, err = os.OpenFile(filename, os.O_RDWR|os.O_APPEND, os.ModePerm)
		if nil != err {
			return
		}
		csvwriter := csv.NewWriter(f)

		// 写数据
		var d []string
		d = append(d, quote.Symbol)
		d = append(d, quote.SymbolT)
		d = append(d, quote.Exchange)
		d = append(d, strconv.Itoa(int(quote.Datetime)))
		d = append(d, quote.DatetimeStr)
		d = append(d, strconv.FormatFloat(quote.AskPrice5, 'f', 4, 64))
		d = append(d, strconv.Itoa(int(quote.AskVolume5)))
		d = append(d, strconv.FormatFloat(quote.AskPrice4, 'f', 4, 64))
		d = append(d, strconv.Itoa(int(quote.AskVolume4)))
		d = append(d, strconv.FormatFloat(quote.AskPrice3, 'f', 4, 64))
		d = append(d, strconv.Itoa(int(quote.AskVolume3)))
		d = append(d, strconv.FormatFloat(quote.AskPrice2, 'f', 4, 64))
		d = append(d, strconv.Itoa(int(quote.AskVolume2)))
		d = append(d, strconv.FormatFloat(quote.AskPrice1, 'f', 4, 64))
		d = append(d, strconv.Itoa(int(quote.AskVolume1)))
		d = append(d, strconv.FormatFloat(quote.BidPrice1, 'f', 4, 64))
		d = append(d, strconv.Itoa(int(quote.BidVolume1)))
		d = append(d, strconv.FormatFloat(quote.BidPrice2, 'f', 4, 64))
		d = append(d, strconv.Itoa(int(quote.BidVolume2)))
		d = append(d, strconv.FormatFloat(quote.BidPrice3, 'f', 4, 64))
		d = append(d, strconv.Itoa(int(quote.BidVolume3)))
		d = append(d, strconv.FormatFloat(quote.BidPrice4, 'f', 4, 64))
		d = append(d, strconv.Itoa(int(quote.BidVolume4)))
		d = append(d, strconv.FormatFloat(quote.BidPrice5, 'f', 4, 64))
		d = append(d, strconv.Itoa(int(quote.BidVolume5)))
		d = append(d, strconv.FormatFloat(quote.LastPrice, 'f', 4, 64))
		d = append(d, strconv.FormatFloat(quote.Highest, 'f', 4, 64))
		d = append(d, strconv.FormatFloat(quote.Lowest, 'f', 4, 64))
		d = append(d, strconv.FormatFloat(quote.Open, 'f', 4, 64))
		d = append(d, strconv.FormatFloat(quote.Average, 'f', 4, 64))
		d = append(d, strconv.Itoa(int(quote.Volume)))
		d = append(d, strconv.FormatFloat(quote.Amount, 'f', 4, 64))
		d = append(d, strconv.FormatFloat(quote.Close, 'f', 4, 64))
		d = append(d, strconv.Itoa(int(quote.OpenInterest)))
		d = append(d, strconv.FormatFloat(quote.UpperLimit, 'f', 4, 64))
		d = append(d, strconv.FormatFloat(quote.LowerLimit, 'f', 4, 64))
		d = append(d, strconv.Itoa(int(quote.PreOpenInterest)))
		d = append(d, strconv.FormatFloat(quote.PreSettlement, 'f', 4, 64))
		d = append(d, strconv.FormatFloat(quote.PreClose, 'f', 4, 64))
		d = append(d, strconv.Itoa(int(quote.LocalTimestamp)))
		_ = csvwriter.Write(d)
		csvwriter.Flush()
	}
}
